package hardcode2013;

import com.google.appengine.api.datastore.*;
import com.google.appengine.api.datastore.Query.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class AccountServ extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	public static boolean checkAccountExists(String username) {
		Key accountKey = KeyFactory.createKey("Account", username.toLowerCase());
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		try {
			datastore.get(accountKey);
			return true;
		} catch (Exception ex) {
			return false;
		}
	}
	
	public static String getUsernameCase(String username) {
		AccountProfile acc = getAccountProfile(username);
		if (acc != null) return acc.getUsername();
		else return null;
	}
	
	private static boolean validateAccount(String username, String password) {
		Key accountKey = KeyFactory.createKey("Account", username.toLowerCase());
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		try {
			Entity account = datastore.get(accountKey);
			if (account.getProperty("password").equals(password)) return true;
			else return false;
		} catch (Exception ex) {
			return false;
		}
	}
	
	public static Entity getAccountEntity(String username) {
		Key accountKey = KeyFactory.createKey("Account", username.toLowerCase());
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		try {
			return datastore.get(accountKey);
		} catch (Exception ex) {
			return null;
		}
	}
	
	public static boolean isAdmin(String username) {
		AccountProfile acc = getAccountProfile(username);
		return acc.getIsAdmin();
	}
	
	public static AccountProfile getAccountProfile(String username) {
		Key accountKey = KeyFactory.createKey("Account", username.toLowerCase());
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		try {
			Entity account = datastore.get(accountKey);
			return new AccountProfile(
					(String) account.getProperty("username"),
					(Boolean) account.getProperty("use_google"),
					(account.getProperty("google_email") != null ? (String) account.getProperty("google_email") : null),
					(account.getProperty("password") != null ? (String) account.getProperty("password") : null),
					(Boolean) account.getProperty("is_admin"),
					(Boolean) account.getProperty("active"));
		} catch (Exception ex) {
			return null;
		}
	}

	public static String getEmailUsername(String email) {
	    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
	    Query query = new Query("Account");
	    query = query.setFilter(new FilterPredicate("google_email", FilterOperator.EQUAL, email.toLowerCase()));	
	    QueryResultList<Entity> queryResList = datastore.prepare(query).asQueryResultList(FetchOptions.Builder.withDefaults());
	    for (Entity e : queryResList) {
	    	String username = (String) e.getProperty("username");
	    	return username;
	    }
	    return null;
	}
	
	public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		String action = req.getParameter("action");
		if (action == null) {HttpUtil.HomeRedirect(resp); return;}
		
		//--- Create --- : Guest
		if (action.equals("create_account")) { //username, password
			//load
			String type = req.getParameter("type");
			
			if (type.equals("standard")) {
				//load
				String username = req.getParameter("username");
				String password = req.getParameter("password");
				String confirmPassword = req.getParameter("confirm_password");
				boolean isAdmin = req.getParameter("admin") != null;
				
				//check
				if (username == null || username.trim().isEmpty())
					{HttpUtil.ExitWithMsg("Username cannot be empty.", req, resp); return;}
				else if (Pattern.compile("[^a-zA-Z0-9]").matcher(username).find())
					{HttpUtil.ExitWithMsg("Username can only contain alphabets and numbers.", req, resp); return;}
				else if (! password.equals(confirmPassword))
					{HttpUtil.ExitWithMsg("Passwords do not match.", req, resp); return;}
				else if (password == null || password.isEmpty())
					{HttpUtil.ExitWithMsg("Password cannot be empty.", req, resp); return;}
				else if (checkAccountExists(username.trim()))
					{HttpUtil.ExitWithMsg("Username already exists.", req, resp); return;}
				
				//perform
				username = username.trim();
				Entity account = new Entity("Account", username.toLowerCase());
				account.setProperty("username", username);
				account.setProperty("use_google", false);
				account.setProperty("password", password);
				account.setProperty("google_email", null);
				account.setProperty("is_admin", isAdmin);
				account.setProperty("active", true);
				DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
				datastore.put(account);
				SessionUtil.setAuthUsername(req, username);
				AdminServ.logEvent(username, "Created Account");
				SessionUtil.setMessage(req, "Account created successfully.");
			
			} else if (type.equals("google")) {
				//load
				String googleEmail = GoogleUtil.getEmail();
				String nickname = req.getParameter("nickname");
				boolean isAdmin = req.getParameter("admin") != null;
				
				//check
				if (googleEmail == null)
					{HttpUtil.ExitWithMsg("You must be logged in to Google.", req, resp); return;}
				if (nickname == null || nickname.trim().isEmpty())
					{HttpUtil.ExitWithMsg("Nickname cannot be empty.", req, resp); return;}
				else if (Pattern.compile("[^a-zA-Z0-9]").matcher(nickname).find())
					{HttpUtil.ExitWithMsg("Nickname can only contain alphabets and numbers.", req, resp); return;}
				else if (checkAccountExists(nickname.trim()))
					{HttpUtil.ExitWithMsg("Username already exists.", req, resp); return;}
				else if (getEmailUsername(googleEmail) != null)
					{HttpUtil.ExitWithMsg("Google account already registered.", req, resp); return;}
				
				//perform
				nickname = nickname.trim();
				Entity account = new Entity("Account", nickname.toLowerCase());
				account.setProperty("username", nickname);
				account.setProperty("use_google", true);
				account.setProperty("password", null);
				account.setProperty("google_email", googleEmail.toLowerCase());
				account.setProperty("is_admin", isAdmin);
				account.setProperty("active", true);
				DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
				datastore.put(account);
				SessionUtil.setAuthUsername(req, nickname);
				AdminServ.logEvent(nickname, "Created Account");
				SessionUtil.setMessage(req, "Account created successfully.");
			}
			
			//--- Delete --- : Member
		} else if (action.equals("delete_account")) { //password
			
			//load
			String authKey = req.getParameter("auth_key");
			String username = SessionUtil.getAuthUsername(req);
			String password = req.getParameter("password");
			
			//check
			if (! SessionUtil.authUser(req, username, authKey))
				{HttpUtil.HomeRedirect(resp); return;}
			else if (password == null || password.isEmpty())
				{HttpUtil.ExitWithMsg("Password cannot be empty.", req, resp); return;}
			else if (! validateAccount(username, password))
				{HttpUtil.ExitWithMsg("Account is invalid.", req, resp); return;}
			
			//perform
			DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
			Query query = new Query("Item");
			query = query.setFilter(new FilterPredicate("seller", FilterOperator.EQUAL, username));
			QueryResultList<Entity> queryResList = datastore.prepare(query).asQueryResultList(FetchOptions.Builder.withDefaults());
			List<Key> keyList = new ArrayList<Key>();
			for (Entity e : queryResList)
				keyList.add(e.getKey());
			Key accountKey = KeyFactory.createKey("Account", username.toLowerCase());
			keyList.add(accountKey);
			datastore.delete(keyList);
	        SessionUtil.removeAuthUsername(req);
	        AdminServ.logEvent(username, "Deleted Account");
	        SessionUtil.setMessage(req, "Account deleted successfully.");
			
			//--- Change Password --- : Member
		} else if (action.equals("change_password")) { //oldPassword, newPassword, newConfirmPassword
			
			//load
			String authKey = req.getParameter("auth_key");
			String username = SessionUtil.getAuthUsername(req);
			String oldPassword = req.getParameter("old_password");
			String newPassword = req.getParameter("new_password");
			String newConfirmPassword = req.getParameter("new_confirm_password");
			
			//check
			if (! SessionUtil.authUser(req, username, authKey))
				{HttpUtil.HomeRedirect(resp); return;}
			else if (oldPassword == null || oldPassword.isEmpty())
				{HttpUtil.ExitWithMsg("Old password cannot be empty.", req, resp); return;}
			else if (! newPassword.equals(newConfirmPassword))
				{HttpUtil.ExitWithMsg("New passwords do not match.", req, resp); return;}
			else if (newPassword == null || newPassword.isEmpty())
				{HttpUtil.ExitWithMsg("New password cannot be empty.", req, resp); return;}
			else if (! validateAccount(username, oldPassword))
				{HttpUtil.ExitWithMsg("Account is invalid.", req, resp); return;}
			
			//perform
			Entity account = getAccountEntity(username);
			account.setProperty("password", newPassword);
			DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
	        datastore.put(account);
	        AdminServ.logEvent(username, "Changed Password");
	        SessionUtil.setMessage(req, "Account password changed successfully.");
	        
			//--- Login --- : Guest
		} else if (action.equals("login_account")) { //password
			
			//load
			String type = req.getParameter("type");
			
			if (type.equals("standard")) {
				//load
				String username = req.getParameter("username");
				String password = req.getParameter("password");
				
				//check
				if (username == null || username.trim().isEmpty())
					{HttpUtil.ExitWithMsg("Username cannot be empty.", req, resp); return;}
				else if (password == null || password.isEmpty())
					{HttpUtil.ExitWithMsg("Password cannot be empty.", req, resp); return;}
				else if (! validateAccount(username.trim(), password))
					{HttpUtil.ExitWithMsg("Account is invalid.", req, resp); return;}
				Entity account = getAccountEntity(username.trim());
				if (! (Boolean) account.getProperty("active"))
					{HttpUtil.ExitWithMsg("Account has been deactivated.", req, resp); return;}
				
				//perform
				SessionUtil.setAuthUsername(req, account.getProperty("username").toString());
				SessionUtil.setAuthKey(req, account.getProperty("username").toString());
				
			} else if (type.equals("google")) {
				//load
				String googleEmail = GoogleUtil.getEmail();
				
				//check
				if (googleEmail == null)
					{HttpUtil.ExitWithMsg("You must be logged in to Google.", req, resp); return;}
				String username = getEmailUsername(googleEmail);
				if (username == null) {
					{HttpUtil.ExitWithMsg("Google account is not registered.", req, resp); return;}
				}
				Entity account = getAccountEntity(username.trim());
				if (! (Boolean) account.getProperty("active"))
					{HttpUtil.ExitWithMsg("Account has been deactivated.", req, resp); return;}
				
				//perform
				SessionUtil.setAuthUsername(req, account.getProperty("username").toString());
				SessionUtil.setAuthKey(req, account.getProperty("username").toString());
			}
		}
		
		HttpUtil.FinalRedirect(req, resp);
	}
	
	public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		String action = req.getParameter("action");
		if (action == null) {HttpUtil.HomeRedirect(resp); return;}
		
		//--- Logout --- : Guest
		if (action.equals("logout_account")) { //nothing
			SessionUtil.removeAuthUsername(req);
		}
		
		HttpUtil.FinalRedirect(req, resp);
	}
}
